/*
 * Copyright (C) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

/**
 * The root of observables hierarchy.
 * Has identifier.
 * <p>
 * The Observer is the class which changes can be observed.
 * </p>
 * <p>
 * Silent Observer can not say anything else about his changes (not contains listeners).
 * </p>
 * @author Tomas Shestakov
 */
trait SilentObservable extends Identifiable

/**
 * Observable which contains an observable value.
 * <p>
 * Silent Observer can not say anything else about his changes (not contains listeners).
 * </p>
 * @tparam A type of `SilentObservable` value
 * @author Tomas Shestakov
 */
trait SilentObservableValue[+A] extends SilentObservable {

  /**
   * @return an observable value.
   */
  def apply(): A
}

object SilentObservableValue {

  def apply[A](value: A, id: Identifier = Identifier.empty) = SilentObservableValueSupport(value, id)

  def unapply[A](obsValue: SilentObservableValue[A]): Option[(A, Identifier)] =
    if (obsValue eq null) None
    else Some(obsValue(), obsValue.id)

  /**
   * Simple implementation of immutable [[jmmo.engine.property.SilentObservableValue]]
   * @param id identifier of observable.
   * @param apply an observable value.
   * @tparam A type of `SilentObservable` value
   */
  private[SilentObservableValue] case class SilentObservableValueSupport[+A](apply: A, id: Identifier = Identifier.empty) extends SerializedObservableValue[A] {
    override def toString = "SilentObservableValue(" + this.apply + "," + id  + ")"
  }
}

/**
 * Observable which contains other observables.
 * <p>
 * Silent Observer can not say anything else about his changes (not contains listeners).
 * </p>
 * @author Tomas Shestakov
 */
trait SilentObservableContainer extends SilentObservable {

  /**
   * Search an child observable with specified id's in this container.
   * If ids is empty returns self wrapped in Option.
   * @param ids identifiers of observables to search.
   * @return child observable with specified id's wrapped in Option.
   */
  def observable(ids: Identifier*): Option[SilentObservable]

  /**
   * @return iterable collection of all observables in this container.
   */
  def observables: Iterable[SilentObservable]
}

object SilentObservableContainer {

  def apply(observables: Iterable[SilentObservable], id: Identifier = Identifier.empty) =
    SilentObservableContainerSupport(Map(observables.map (obs => obs.id -> obs).toSeq: _*), id)

  def unapply(obsContainer: SilentObservableContainer): Option[(Iterable[SilentObservable], Identifier)] =
    if (obsContainer eq null) None
    else Some(obsContainer.observables, obsContainer.id)

  /**
   * Simple implementation of immutable [[jmmo.engine.property.SilentObservableContainer]]
   * @param observablesMap a Map with keys as `id` and value as `SilentObservable`.
   * @param id identifier of observable.
   */
  private[SilentObservableContainer] case class SilentObservableContainerSupport(
        observablesMap: scala.collection.Map[Identifier, SilentObservable], id: Identifier = Identifier.empty)
                                                                                    extends SerializedObservableContainer {
    def observable(ids: Identifier*) = {
      if (ids.isEmpty) {
        Some(this)
      }
      else {
        observablesMap.get(ids.head) match {
          case Some(container: SilentObservableContainer) => container.observable(ids.tail: _*)
          case Some(observable) if ids.tail.isEmpty => Some(observable)
          case _ => None
        }
      }
    }
    def observables = observablesMap.values
    override def toString = "SilentObservableContainer(" + observables.mkString("children(", ",", ")") + "," + id + ")"
  }
}

